

// 进程监控
//系统指标 --> 系统类型,版本 -->cpu 型号 运行指标 // 内存运行指标 //磁盘运行指标 //网卡
//web shell
//自定义监控项目  --> 执行命令 --> 判断返回值 or 检索关键字
//实时的运行情况 v
             // -> IP 主机名
             // -> cpu 内存 磁盘 运行百分比
             // -> 上次系统登录 last

//安全 --v
         //历史命令 远程存放
         //设置本地超时时间


//server端

      //定时巡检ip列表 更新最新状态
      //配置自定义监控项目
      //ansible 的调用接口 实时、定时、终止 ws通道
      //ffskey agent <-> server 认证的key
      //Nfs 共享存储
      //cmdb 资产管理接口
      //server status 接口
      //流程 创建, 查询接口  --> mysql && gorm
      //

package main

import (
	"bufio"
	"bytes"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"github.com/shirou/gopsutil/cpu"
	"github.com/shirou/gopsutil/disk"
	"github.com/shirou/gopsutil/host"
	"github.com/shirou/gopsutil/mem"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)


type Diskinfo struct{
	Errmsg string  `json:"errmsg"`
	Disknum int `json:"disknum"`
	Data []Datas `json:"good"`
}

type Datas struct{
	Device     string `json:"device"`
	Mountpoint string `json:"mountpoint"`
	Fstype     string `json:"fstype"`
	Opts       string `json:"opts"`
}

func runInLinux2(cmd string) (string, error) {
	//fmt.Println("Running Linux cmd:" + cmd)
	result, err := exec.Command("/bin/sh", "-c", cmd).Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(result)), err
}

func runInLinux(cmd2 string) (string,error) {
	cmd := exec.Command("/bin/bash", "-c", cmd2)

	//创建获取命令输出管道
	stdout, err := cmd.StdoutPipe()
	cmd.Stderr = cmd.Stdout
	if err != nil {
		fmt.Printf("Error:can not obtain stdout pipe for command:%s\n", err)

	}

	//执行命令
	if err := cmd.Start(); err != nil {
		fmt.Println("Error:The command is err,", err)

	}

	//读取所有输出
	bytes, err := ioutil.ReadAll(stdout)
	if err != nil {
		fmt.Println("ReadAll Stdout:", err.Error())

	}

	//if err := cmd.Wait(); err != nil {
	//	fmt.Println("wait:", err.Error())
	//
	//}
	return  string(bytes),err
}



func getdisk(c *gin.Context)  {
	var Diskinfos Diskinfo
	Diskinfos.Errmsg = "79018"
	num := 0
	infos, _ := disk.Partitions(false)
	for _, info := range infos {
		_, _ = json.MarshalIndent(info, "", "  ")
		num ++
		Diskinfos.Disknum = num
		Diskinfos.Data =append(Diskinfos.Data,Datas{Device: info.Device,Mountpoint:info.Mountpoint,Fstype:info.Fstype,Opts: info.Opts})
	}
	c.JSON(200,Diskinfos)
}

func diskuseprecent(c *gin.Context) {
	path := c.PostForm("path")
	info, _ := disk.Usage(path)
	data, _ := json.MarshalIndent(info, "", "  ")
	fmt.Println(string(data))
	c.JSON(200,info)
}

func memget(c *gin.Context) {
	v, err := mem.VirtualMemory()
	if err != nil{
		return
	}
	c.JSON(200,v)
}

func cpuinfo(c *gin.Context){
	infos, _ := cpu.Info()
	var cpuname string
	var num int
	for _, info := range infos {
		data, _ := json.MarshalIndent(info, "", " ")
		fmt.Print(string(data))
        		num ++
        		cpuname = info.ModelName
	}
	c.JSON(200,gin.H{
		"cpuname": 	cpuname,
		"core" : num,
	})

}

func getCpuLoad(c *gin.Context) {
	totalPercent, _ := cpu.Percent(2*time.Second, false)
	strKm := fmt.Sprintf("%.2f", totalPercent)
	t2 := strings.Split(strKm, "[")
	t3 := strings.Split(t2[1], "]")
	floatvar, _ := strconv.ParseFloat(t3[0], 64)
	c.JSON(200,gin.H{
		"cpuload": 	floatvar ,
	})
}

func getHostInfo(c *gin.Context) {
	hInfo, _ := host.Info()
	//fmt.Printf("host info:%v uptime:%v boottime:%v\n", hInfo, hInfo.Uptime, hInfo.BootTime)
	c.JSON(200,hInfo)
}

func GetLocalIP() (ip string, err error) {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return
	}
	for _, addr := range addrs {
		ipAddr, ok := addr.(*net.IPNet)
		if !ok {
			continue
		}
		if ipAddr.IP.IsLoopback() {
			continue
		}
		if !ipAddr.IP.IsGlobalUnicast() {
			continue
		}
		return ipAddr.IP.String(), nil
	}
	return
}

func getipend(c *gin.Context)  {
	x ,_ := GetLocalIP()
	c.JSON(200,gin.H{
		"IP":x,
	})
}

var listenmap map[string]string
//func listenbypid(pid string){
//
//	for {
//
//	}
//}

type AOrder struct{
	Errmsg string  `json:"errmsg"`
	Goods []AGood `json:"good"`
}

type AGood struct{
	GoodsName string `json:"goods_name"`
	Goodsstatus string `json:"goods_status"`
	Goodsinfo string `json:"goods_info"`
}

func listenlist(c *gin.Context){
	var AOrderInfo AOrder
	for k,x := range listenmap {
		lip := strings.Split(x,";")
		status := lip[0]
		findps := lip[1]
		restart := lip[2]
		elseinfo := lip[3]
		ltype := lip[4]
		listeninfo := "查找: " +findps + "," + "重启命令: "+ restart +"," + "备注: " + elseinfo +"," + "类型: " +ltype
		AOrderInfo.Errmsg = "001"
		AOrderInfo.Goods =append(AOrderInfo.Goods,AGood{GoodsName:k,Goodsstatus:status,Goodsinfo:listeninfo})
	}
	c.JSON(200,AOrderInfo)
}

func dellisten(c * gin.Context){
	name := c.PostForm("name")
	delete(listenmap ,name)
	fmt.Println(listenmap)
	bakfile()
	c.JSON(200,gin.H{
		"err" : 10086,
		"msg" : "success",
		})
}

//data := "waitstart" + ";" + lname + ";" + startcmd + ";" + elseinfo + ";" + ltype
func rangemapforlisten(){
	for {
		for _,v := range listenmap {
			lip := strings.Split(v,";")
			status := lip[0]
			findps := lip[1]
			restart := lip[2]
			elseinfo := lip[3]
			ltype := lip[4]
			if ltype == "name" {
				go listenbyname(findps,elseinfo,status,findps,restart)
			}
		}

		time.Sleep(time.Second * 10)
	}
}

func bakfile(){
	//data := "waitstart" + ";" + lname + ";" + startcmd + ";" + elseinfo + ";" + ltype
		var datas string
		for k,x := range listenmap {
			name := k
			lip := strings.Split(x,";")
			findps := lip[1]
			restart := lip[2]
			elseinfo := lip[3]
			ltype := lip[4]
			fmt.Println(datas)
			datas = datas + "\n" + ltype + ";" + findps + ";" + name + ";"+ restart +";" + elseinfo + "\n"
		}
		err := writefile("listenfile",datas)
		if err != nil {
			fmt.Println("bak listenfile:", err)
		}
}



func writefile(filename string,nei string) error{
	fileName := filename
	f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
	if err != nil {
		fmt.Println("file create failed. err: " + err.Error())
	} else {

		content := nei
		n, _ := f.Seek(0, os.SEEK_END)
		_, err = f.WriteAt([]byte(content), n)

		defer f.Close()
	}
	return err
}


func listenfile_rebak(){
	//ltype + ";" + findps + ";" + name + ";"+ restart +";" + elseinfo + "\n"
	//data := "waitstart" + ";" + lname + ";" + startcmd + ";" + elseinfo + ";" + ltype
	fmt.Println("正在读取进程监控配置......")

		file, err := os.Open("listenfile")
		if err != nil {
			fmt.Println(err)
		}
		fileScanner := bufio.NewScanner(file)
		for fileScanner.Scan(){
			if fileScanner.Text() == ""{
				continue
			}
			//name;mysql;systemctl restart mysql;test
			v := strings.Split(fileScanner.Text(),";")
			ltype := v [0]
			name := v[1]
			restart := v[3]
			elseinfo := v[4]
			listenmap [elseinfo] = "waitcheck" + ";" + name+ ";" + restart + ";" + elseinfo + ";"  + ltype
		}

	fmt.Println("读取完毕读取进程监控配置......")
}

var BlShell string
func listenmyself() {
	fileName := "alert.sh"
	f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
	if err != nil {
		fmt.Println("file create failed. err: " + err.Error())
	} else {
		// offset
		//os.Truncate(filename, 0) //clear
		content := `cpwd=$PWD
cd $cpwd
while true
do
ps aux | grep -v grep | grep ffs.agent
if [ $? -ne 0 ]
then
nohup $cpwd/ffs.agent $* -restart yes>ffs.log 2>ffs.log &
fi
sleep 3
done
`
		n, _ := f.Seek(0, os.SEEK_END)
		_, err = f.WriteAt([]byte(content), n)
		fmt.Println("write succeed!")
		defer f.Close()
	}
	_ ,err3 := runInLinux2("ps aux | grep alert.sh | grep -v grep | awk '{print $2}' | xargs kill")
	if err3 != nil {
		fmt.Println(err3)
	}
	p := "nohup sh ./alert.sh" + " " +BlShell  + " "  +">/dev/null 2>/dev/null &"
	fmt.Println(p)
	_ ,err2 := runInLinux2(p)
	if err2 != nil {
		fmt.Println(err2)
	}
	return
}

var server_ip string
var server_port string

func update_listen_status_to_apiserver(ip string,elseinfo string,startcmd string,name string,status string) {

	url := "http://"+ server_ip + ":" + server_port + "/listen_data_from_agent"
	method := "POST"

	payload := &bytes.Buffer{}
	writer := multipart.NewWriter(payload)
	_ = writer.WriteField("ip", ip)
	_ = writer.WriteField("else", elseinfo)
	_ = writer.WriteField("startcmd", startcmd)
	_ = writer.WriteField("name", name)
	_ = writer.WriteField("status", status)
	err := writer.Close()
	if err != nil {
		fmt.Println(err)
		return
	}


	client := &http.Client {
	}
	req, err := http.NewRequest(method, url, payload)

	if err != nil {
		fmt.Println(err)
		return
	}
	req.Header.Set("Content-Type", writer.FormDataContentType())
	res, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(string(body))
}


func listenbyname(name string,elseinfo string,status string,findps string,restart string){
	ip ,_ := GetLocalIP()
	findcmd := "ps aux | grep -v grep | grep " + name
       outinfo ,_ := runInLinux(findcmd)
	   if outinfo == ""{
	   	fmt.Println(name,elseinfo," not found ")
		   listenmap [elseinfo] = "Bad" + ";" + findps + ";" + restart + ";" + elseinfo + ";"  + "name"
		   update_listen_status_to_apiserver(ip,elseinfo,restart,name,"Bad")
		   fmt.Printf("%v is down,restartcmd:%v will be do\n",name,restart)
		   out,_ := runInLinux(restart)
		   fmt.Println(out)
		   fmt.Printf("命令: (%v) ^ ^ ^ 执行完毕 等待下一次检查\n",restart)
	   }else {
		   listenmap [elseinfo] = "OK" + ";" + findps + ";" + restart + ";" + elseinfo + ";"  + "name"
		   update_listen_status_to_apiserver(ip,elseinfo,restart,name,"OK")
		   fmt.Println(name,elseinfo, " Running")
	   }
}


func lstenps(c * gin.Context){
   ltype := c.PostForm("ltype")
   lname := c.PostForm("lname")
   elseinfo:= c.PostForm("else")
   startcmd := c.PostForm("startcmd")
   if ltype == "pid"{
    fmt.Println(ltype)
   }else if ltype == "name" {
   	c.JSON(200,gin.H{
   		"errmsg" : 0,
   		"msg": "success",
   		"msg2": "list from API -> /listenlist",
	})
   	 data := "waitstart" + ";" + lname + ";" + startcmd + ";" + elseinfo + ";" + ltype
	 listenmap [elseinfo] = data
	 fmt.Println(listenmap)
	 fmt.Println("开始写入")
	 bakfile()
   }else {
   	c.JSON(200,gin.H{
   		"error" : 10088,
   		"msg" : "choos type:pid or name",
   		"msg2" : "and post that",
	})
   }
}


type userlist struct{
	Errmsg string  `json:"errmsg"`
	Goods []userlist_from `json:"good"`
}

type userlist_from struct{
	Logname string `json:"logname"`
}


func replay(c *gin.Context){
	var usercmdlog string
	var userlists userlist
	username := c.PostForm("username")
	files, err := ioutil.ReadDir("/var/log/script/")
	if err != nil {
		panic(err)
	}
	for _, file := range files {
		Index := strings.Index(file.Name(),".log")
		if Index != -1 {
			indexuser := strings.Index(file.Name(),username)
			if indexuser != -1 {
				usercmdlog = file.Name()
				userlists.Errmsg = "002"
				userlists.Goods =append(userlists.Goods,userlist_from{Logname: usercmdlog})
			}
		}
	}
	out ,_ := json.Marshal(userlists)
	fmt.Println(string(out))
	c.JSON(200,userlists)
}


//回放

const (

	base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

)

var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

var coder = base64.NewEncoding(base64Table)

func base64jiemi(src []byte) ([]byte, error) {
	return coder.DecodeString(string(src))

}
func base64jiami(src []byte) []byte {

	return []byte(coder.EncodeToString(src))

}

func ws(context *gin.Context){
		aq := context.Query("run")
		client, _ := upgrader.Upgrade(context.Writer, context.Request, nil)
	    aq2,_ := base64jiemi([]byte(aq))
	    aq3,_ := base64jiemi([]byte(aq2))
	    fmt.Println(aq,string(aq3))
		cmd := exec.Command(ShellToUse, "-c",string(aq3))
		stdout, _ := cmd.StdoutPipe() //正确输出
		cmd.Stderr = cmd.Stdout //错误重定向
		err := cmd.Start()
		if err !=nil {
			client.WriteMessage(websocket.TextMessage, []byte(error.Error(err)))
		}
		client.WriteMessage(websocket.TextMessage, []byte("即将开始执行(现在可以终止执行)"))
		reader := bufio.NewReader(stdout)

		for {
			line, err2 := reader.ReadString('\n')

			if err2 != nil || io.EOF == err2 {
				break
			}

			fmt.Printf("Time:%v\n",line)
			err := client.WriteMessage(websocket.TextMessage, []byte(line))

			if err != nil{
				fmt.Println(err)
			}
		}

		err = client.WriteMessage(websocket.TextMessage, []byte("-->  执行完毕 !"))
		if err != nil{
			fmt.Println(err)
		}
		cmd.Wait()

}



func wsurl(c *gin.Context){
	logname := c.PostForm("logname")
	url := "cd /var/log/script && scriptreplay " + logname + ".date" + " " + logname  + ".log"
     fmt.Println(url)
	url2 := base64jiami([]byte(url))
	c.JSON(200,gin.H{
		"code" : 10011,
		"url" : url2,
	})

}


func FileDownload(c *gin.Context) {
    filename := c.Query("filename")
    path := "/var/log/script/" + filename
	c.Writer.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) //fmt.Sprintf("attachment; filename=%s", filename)对下载的文件重命名
	c.Writer.Header().Add("Content-Type", "application/octet-stream")
	c.File(path)//文件在当前目录
	//fmt.Printf("wwffa")
}
const ShellToUse = "bash"
func shellrun(command string) (error, string, string) {
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	cmd := exec.Command(ShellToUse, "-c", command)
	fmt.Println(command)
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()
	return err, stdout.String(), stderr.String()
}


var customdata map[string]string



// [ 数据名字 ] = {命令} ,{结果}
// [监控磁盘vdb1] = {df -h | grep 'vdb1' | awk '{print $5}' | awk -F % '{print $1}'}, {36}


func makecuntom(c *gin.Context){
	name := c.PostForm("name")
	run := c.PostForm("run")
	data := run + ";" + ""
	customdata [name] = data
}

func getcutomlist(c *gin.Context){
		for k,v := range customdata {
			lip := strings.Split(v, ";")
			c.JSON(200,gin.H{
				"name": k,
				"shell" : lip[0],
				"out" : lip[1],
			})
		}
}

func custom(){
     for{
     	for k,v := range customdata {
         go goruncustom(k,v)
		}
		time.Sleep(time.Second * 10)
	 }
}

func goruncustom(k string,v string){
	name := k
	lip := strings.Split(v,";")
	shell := lip[0]
	err,out1,out2 := shellrun(shell)
	if err != nil {
		fmt.Println("Run custom err:",err)
	}
	if len(out1) == 0{
		returndata := shell + ";" + out2
		customdata [name] = returndata
	}else if len(out2) == 0{
		returndata := shell + ";" + out1
		customdata [name] = returndata
	}else {
		fmt.Println("Run cunstom err by retrun")
	}
}

//const (
//
//	base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
//
//)
//
//var coder = base64.NewEncoding(base64Table)
func jiemi(src []byte) ([]byte, error) {

	return coder.DecodeString(string(src))

}

func longshell(c *gin.Context){
	shell := c.Query("shell")
	cmd,_ := jiemi([]byte(shell))
	shell2 := string(cmd)

     index := strings.Index(shell2,"rm")
     if index == -1 {
		 err, out1, out2 := shellrun(shell2)
		 if err != nil {
			 fmt.Println(err)
		 }

		 if len(out1) == 0 {
			 c.JSON(200, gin.H{
				 "code": 10092,
				 "return_code": 1,
				 "msg":  out2,
			 })
		 }else if len(out2) == 0 {
			 c.JSON(200, gin.H{
				 "code": 10092,
				 "return_code": 0,
				 "msg":  out1,
			 })
		 }

	 }else {
		 c.JSON(200, gin.H{
		 	"code" : 10091,
		 	"msg" : "index rm in shell",
		 })
	 }
}

func main() {

	var ip string
	flag.StringVar(&ip, "ip", "", "node ip")
	var port string
	flag.StringVar(&port, "port", "8081", "node port")
	var restart string
	flag.StringVar(&restart, "restart", "", "restart if down")
	var version string
	flag.StringVar(&version, "v", "", "version")
	var agentport string
	flag.StringVar(&agentport, "agentport", "8080", "agentport")
	flag.Parse()

	if len(version) != 0{
		fmt.Printf("Version Aegnt For Linux: %v and Apply to Centos,Ubuntu,Debian\n","LIP-0.1.17-Agent")
		fmt.Printf("The code is written by: %v\n\n","Gs-engineer")
		os.Exit(0)
	}

	if len(ip) == 0{
		fmt.Println("请输入apiserver地址, -ip ")
		os.Exit(0)
	}else {
		server_ip = ip
		server_port = port
		BlShell  = "-ip " +ip +" " +"-port " + port
	}
	listenmap = make(map[string]string)
	customdata = make(map[string]string)

	listenfile_rebak()
	go rangemapforlisten()
	go custom()
	if restart == "yes" {
       fmt.Printf("自动重启已开启: %v",BlShell)
		go listenmyself()
	}else {
		fmt.Println("未配置自动重启 如果需要 -restart yes")
	}

	fmt.Printf("Apiserver:%v Port%v",ip,port)

	server := gin.Default()
	server.GET("/getmem",memget)
	server.GET("/getcpuinfo",cpuinfo)
	server.GET("/gethostinfo",getHostInfo)
	server.GET("/getip",getipend)
	server.GET("/getdisk",getdisk)
	server.GET("/getdiskdetails",diskuseprecent)
	server.GET("/getcpuload",getCpuLoad)
	server.GET("listenlist",listenlist)
	server.POST("/makelisten",lstenps)
	server.GET("/dellisten",dellisten)
	server.GET("/userreplay",replay)
	server.GET("/ws",ws)
	server.GET("/wsurl",wsurl)
	server.GET("/download",FileDownload)
	server.GET("/shellrunbyquery",longshell)
	//server.POST("/makecustom",makecuntom)
	//server.GET("/getcunstom",getcutomlist)
	server.Run(":" + agentport)
}